package io.advantageous.qbit.service.discovery;

import io.advantageous.qbit.reactive.Callback;
import io.advantageous.qbit.service.Startable;
import io.advantageous.qbit.service.Stoppable;
import io.advantageous.qbit.service.health.HealthStatus;

import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.*;

/**
 * Service Discovery
 * created by rhightower on 3/23/15.
 */
public interface ServiceDiscovery extends Startable, Stoppable {

    /**
     * Generates a unique string, used for generating unique service ids
     *
     * @param port port
     * @return unique id incorporating host name if possible.
     */
    static String uniqueString(int port) {
        try {
            return port + "-" + InetAddress.getLocalHost().getHostName().replace('.', '-');
        } catch (UnknownHostException e) {
            return port + "-" + UUID.randomUUID().toString();
        }
    }

    /**
     * Register the service with the service discovery service if applicable.
     *
     * @param serviceName serviceName
     * @param host        host
     * @param port        port
     * @return EndpointDefinition
     */
    default EndpointDefinition register(
            final String serviceName,
            final String host,
            final int port) {

        return new EndpointDefinition(HealthStatus.PASS,
                serviceName + "." + uniqueString(port),
                serviceName, host, port);
    }

    /**
     * Register with the service discovery system and specify a TTL so that if
     * the service does not send a checkIn that it is marked down.
     * TTL is time to live.
     *
     * @param serviceName       service name
     * @param host              host
     * @param port              port
     * @param timeToLiveSeconds ttl
     * @return EndpointDefinition
     */
    default EndpointDefinition registerWithTTL(
            final String serviceName,
            final String host,
            final int port,
            final int timeToLiveSeconds) {

        return new EndpointDefinition(HealthStatus.PASS,
                serviceName + "." + uniqueString(port),
                serviceName, host, port, timeToLiveSeconds);
    }

    /**
     * Register an end point given an id, and a TTL.
     * This gets used if you want to be specific about what you call the service.
     *
     * @param serviceName       service name
     * @param serviceId         service id
     * @param host              host
     * @param port              port
     * @param timeToLiveSeconds ttl
     * @return EndpointDefinition
     */
    @SuppressWarnings("UnusedReturnValue")
    default EndpointDefinition registerWithIdAndTimeToLive(
            final String serviceName, final String serviceId, String host, final int port, final int timeToLiveSeconds) {

        return new EndpointDefinition(HealthStatus.PASS,
                serviceId,
                serviceName, host, port, timeToLiveSeconds);
    }

    /**
     * Register with id. Specify a unique id that is not autogenerated
     *
     * @param serviceName service name
     * @param serviceId   service id
     * @param host        host
     * @param port        port
     * @return EndpointDefinition
     */
    default EndpointDefinition registerWithId(final String serviceName, final String serviceId, String host, final int port) {

        return new EndpointDefinition(HealthStatus.PASS,
                serviceId,
                serviceName, host, port);
    }

    /**
     * Register an end point given an id, and a TTL with tags.
     * This gets used if you want to be specific about what you call the service.
     *
     * @param serviceName       service name
     * @param serviceId         service id
     * @param host              host
     * @param port              port
     * @param timeToLiveSeconds ttl
     * @param endpointTags      endpointTags
     * @return EndpointDefinition
     */
    @SuppressWarnings("UnusedReturnValue")
    default EndpointDefinition registerWithIdAndTTLAndTags(String serviceName, String serviceId, String host, int port, int timeToLiveSeconds,
                                                           List<String> endpointTags) {
        return new EndpointDefinition(HealthStatus.PASS, serviceId, serviceName, host, port, timeToLiveSeconds, endpointTags);
    }

    /**
     * Watch for changes in this service name and send change events if the service changes.
     *
     * @param serviceName serviceName
     */
    default void watch(String serviceName) {
    }

    /**
     * CheckIn with the service discovery mechanism. The service may be marked as down if it does
     * not check in, in the amount of time specified by the ttl if the service disovery provider supports
     * ttl and checkin (Consul does).
     *
     * @param serviceId    serviceId
     * @param healthStatus healthStatus
     */
    default void checkIn(String serviceId, HealthStatus healthStatus) {

    }

    /**
     * This is like `checkIn` but does an HealthStatus.SUCCESS if applicable.
     *
     * @param serviceId serviceId
     */
    default void checkInOk(String serviceId) {

    }

    /**
     * Load the services.
     * <p>
     * Depending on the underlying implementation the services are either periodically loaded
     * or loaded whenever a change is detected.
     * <p>
     * This version of the method is based on the last event change and / or the last poll of the
     * services from the remote system (i.e., Consul) if applicable.
     * <p>
     * This may also may trigger a remote call, but it will always return right away.
     *
     * @param serviceName service name
     * @return list of EndpointDefinition
     */
    default List<EndpointDefinition> loadServices(final String serviceName) {

        return Collections.emptyList();
    }

    /**
     * Loads services async.
     *
     * @param callback    callback
     * @param serviceName name of service
     */
    default void loadServicesAsync(Callback<List<EndpointDefinition>> callback, final String serviceName) {

    }

    /**
     * See `loadServices` this is like `loadServices` except it forces a remote call.
     * This is a blocking call to loadServices.
     *
     * @param serviceName service name.
     * @return list of EndpointDefinition
     */
    default List<EndpointDefinition> loadServicesNow(final String serviceName) {

        return Collections.emptyList();
    }

    /**
     * Start the service discovery system if applicable.
     */
    default void start() {
    }

    /**
     * Stop the service discovery system if applicable.
     */
    default void stop() {
    }

    /**
     * This just loads the end points that were registered locally.
     * These are the endpoints that this JVM and this ServiceDiscovery is managing.
     *
     * @return set of EndpointDefinitions
     */
    default Set<EndpointDefinition> localDefinitions() {
        return Collections.emptySet();
    }

}
